﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WMM.Tapioca.Taxonomy;
using WMM.Tapioca.Linkbase;
using WMM.Tapioca.Formula.Filters;
using Saxon.Api;
using WMM.Tapioca.Instance;
using System.Xml.Linq;
using System.Text.RegularExpressions;
using WMM.Tapioca.Utilities;
using WMM.Tapioca.XbrlDimension;
using System.Xml;
using System.Collections;
using System.IO;
using WMM.Tapioca.Formula.Functions;
using log4net.Appender;
using log4net.Core;
using WMM.Tapioca.Formula.Aspects;
using log4net.Repository.Hierarchy;


namespace WMM.Tapioca.Formula
{
    /// <summary>
    /// Class that validates the formula linkbase.
    /// </summary>
    public class FormulaProcessor
    {
        public static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private DTS dts;

        Dictionary<string, string> namespaces = new Dictionary<string,string>();

        XNamespace xbrli = Constants.XBRLI;
        XNamespace link = Constants.LINK;
        XNamespace xbrldi = Constants.XBRLDI;
        XNamespace formulaNS = Constants.FORMULA;

        //string nsparameter = "http://xbrl.org/2008/formula/conformance/parameter";

        XDocument doc;
        internal static XElement xbrl;

        internal int formulaSatisfied;
        internal int formulaNotSatisfied;
        internal int assertionsSatisfied;
        internal int assertionsNotSatisfied;

        List<XElement> itemsFormula;

        public List<XElement> ItemsFormula
        {
            get { return itemsFormula; }
            set { itemsFormula = value; }
        }

        IAppender htmlFileAppender;

        /// <summary>
        /// The constructor.
        /// </summary>
        public FormulaProcessor(DTS dtsXbrl, bool createHtml)
        {
            dts = dtsXbrl;
            namespaces = dtsXbrl.DtsFormula.Namespaces;
            itemsFormula = new List<XElement>();

            if (createHtml) // when execute the suite test the createHtml is set to false.
            {
                htmlFileAppender = CreateRollingFileAppender("FormulaRollingHtmlFileAppender", Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\Tapioca\\FormulaMessages.html");
                AddAppender(log.Logger.Name, htmlFileAppender);

                var appenders = log4net.LogManager.GetRepository().GetAppenders();
                SetAppendersLevel(Level.Error);
            }

            try
            {
                EvaluateAssertionSets();

                EvaluateAssertions();

                EvaluateFormulas();            

                if (createHtml) // when execute the suite test the createHtml is set to false.
                {
                    if (assertionsSatisfied + assertionsNotSatisfied > 0)
                    {
                        AppendToFile();
                    }                
                }
            }
            finally
            {
                SetAppendersLevel(Level.Info);
            }            
        }


        /// <summary>
        /// The constructor.
        /// </summary>
        public FormulaProcessor(DTS dtsXbrl, string message)
        {
            dts = dtsXbrl;
            
            log.Info("\n\nFormula trace - " + message);            

        }


        // Add an appender to a logger
        public static void AddAppender(string loggerName, log4net.Appender.IAppender appender)
        {
            log4net.ILog log = log4net.LogManager.GetLogger(loggerName);
            log4net.Repository.Hierarchy.Logger l = (log4net.Repository.Hierarchy.Logger)log.Logger;

            l.RemoveAppender(appender.Name);
            l.AddAppender(appender);
        }

        string layoutPattern = "<tr valign=\"top\" class=\"%level\">\n" +
                               "    <td><strong>%property{assertionId}</strong></td>\n" +
                               "    <td>%property{satisfied}</td>\n" +
                               "    <td>%message</td>\n" +
                               "</tr>\n";

        // Create a new file appender
        public log4net.Appender.IAppender CreateRollingFileAppender(string name, string fileName)
        {
            log4net.Appender.RollingFileAppender appender = new log4net.Appender.RollingFileAppender();
            appender.Name = name;
            appender.Encoding = Encoding.GetEncoding("iso-8859-1");
            appender.File = fileName;
            appender.AppendToFile = false;
            appender.LockingModel = new FileAppender.MinimalLock();
            appender.MaxSizeRollBackups = 1;

            log4net.Layout.PatternLayout layout = new log4net.Layout.PatternLayout();

            layout.ConversionPattern = layoutPattern;
            layout.ActivateOptions();

            appender.Layout = layout;
            appender.ActivateOptions();
            appender.Threshold = Level.Info;

            return appender;
        }


        private void AppendToFile()
        {
            string currentContent = String.Empty;

            if (File.Exists(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\Tapioca\\FormulaMessages.html"))
            {
                currentContent = File.ReadAllText(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\Tapioca\\FormulaMessages.html", Encoding.Default);
            }

            StringBuilder head = new StringBuilder();

            head.AppendLine(@"<html xmlns:xs=""http://www.w3.org/2001/XMLSchema"" xmlns:xbrli=""http://www.xbrl.org/2003/instance"" xmlns:exsl=""http://exslt.org/common"">");
            head.AppendLine(@"<head>");
            //head.AppendLine(@"<meta http-equiv=""Content-Type"" content=""text/html; charset=UTF-8"">");
            //head.AppendLine(@"<meta http-equiv='Content-Type' content='text/html;charset=iso-8859-1'>");
            head.AppendLine(@"     <style type=""text/css""> body { font:normal 68% verdana,arial,helvetica;");
            head.AppendLine(@"          color:#000000; } table tr td, table tr th { font-size: 68%; } table.details tr");
            head.AppendLine(@"          th{ font-weight: bold; text-align:left; background:#a6caf0; } table.details tr");
            head.AppendLine(@"          td{ background:#eeeee0; } p { line-height:1.5em; margin-top:0.5em;");
            head.AppendLine(@"          margin-bottom:1.0em; } h1 { margin: 0px 0px 5px; font: 165%");
            head.AppendLine(@"          verdana,arial,helvetica } h2 { margin-top: 1em; margin-bottom: 0.5em; font: bold");
            head.AppendLine(@"          125% verdana,arial,helvetica } h3 { margin-bottom: 0.5em; font: bold 115%");
            head.AppendLine(@"          verdana,arial,helvetica } h4 { margin-bottom: 0.5em; font: bold 100%");
            head.AppendLine(@"          verdana,arial,helvetica }h5 { margin-bottom: 0.5em; font: bold 100%");
            head.AppendLine(@"          verdana,arial,helvetica } h6 { margin-bottom: 0.5em; font: bold 100%");
            head.AppendLine(@"          verdana,arial,helvetica } .Error { font-weight:bold; color:red; }");
            head.AppendLine(@"          .Warn { font-weight:bold; color:blue;}");
            head.AppendLine(@"          .Failure {font-weight:bold; color:purple; } .Properties { text-align:right; } .IsVisible {");
            head.AppendLine(@"          display: block; } .NotVisible { display: none; } .Expander { cursor: hand;");
            head.AppendLine(@"          font-family: Courier; } .Parent DIV { margin-Left: 30px !important; } </style>");
            head.AppendLine(@"<title>Formula Validation</title>");
            head.AppendLine(@"  </head>");
            head.AppendLine(@"  <body>");
            head.AppendLine(@"	<h1>Formula Validation</h1>");
            head.AppendLine(@"	<h2>" + DateTime.Now.ToString("dddd, yyyy-MM-dd HH:mm tt") + "</h2>");
            head.AppendLine(@"	<h2> Tapioca XBRL Processor Version:1.0</h2>");
            head.AppendLine(@"	<h2> Report name: Detailed Output");

            head.AppendLine(@"	</h2>");
            head.AppendLine(@"	<hr size=""1"">");
            head.AppendLine(@"	<h2> Summary </h2>");
            head.AppendLine(@"	<table width=""95%"" cellspacing=""2"" cellpadding=""5"" border=""0"" class=""details"">");
            head.AppendLine(@"	  <tbody><tr valign=""top"">");
            head.AppendLine(@"	    <th> Formulas Compiled </th>");
            head.AppendLine(@"		<th> Formula Fired </th>");
            head.AppendLine(@"		<th> Assertions Compiled </th>");
            head.AppendLine(@"		<th> Assertions Fired </th>");
            head.AppendLine(@"		<th> Assertions Satisfied </th>");
            head.AppendLine(@"		<th> Assertions Not Satisfied </th>");
            head.AppendLine(@"	   </tr>");
            head.AppendLine(@"	<tr valign=""top"" class=""Pass"">");
            head.AppendLine(@"	<td>" + (formulaSatisfied + formulaNotSatisfied )+ "</td>");
            head.AppendLine(@"	<td>" + (formulaSatisfied + formulaNotSatisfied) + "</td>");
            head.AppendLine(@"	<td>" + (dts.DtsFormula.ValueAssertions.Count + dts.DtsFormula.ExistenceAssertions.Count + dts.DtsFormula.ConsistencyAssertions.Count) + "</td>");
            head.AppendLine(@"	<td>" + (assertionsSatisfied + assertionsNotSatisfied) + "</td>");
            head.AppendLine(@"	<td>" + assertionsSatisfied + "</td>");
            head.AppendLine(@"	<td>" + assertionsNotSatisfied + "</td>");
            head.AppendLine(@"	</tr>");
            head.AppendLine(@"	</tbody></table>");
            head.AppendLine(@"	<hr size=""1"">");
            head.AppendLine(@"	<h2> Assertion Report </h2>");
            head.AppendLine(@"	<table width=""95%"" cellspacing=""2"" cellpadding=""5"" border=""0"" class=""details"">");

            if (GetRootLogger().ToLower() == "error")
            {                
                head.AppendLine(@"	   <tr valign=""top"">");
                head.AppendLine(@"	      <th> id </th>");
                head.AppendLine(@"	      <th> satisfied </th>");
                head.AppendLine(@"	      <th> message </th>");
                head.AppendLine(@"	   </tr>");
            }

            StringBuilder endOfFile = new StringBuilder();

            endOfFile.AppendLine(@"     </table>");

            endOfFile.AppendLine(@"  </body>");
            endOfFile.AppendLine(@"</html>");

            File.WriteAllText(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\Tapioca\\FormulaMessages.html", head.ToString() + currentContent + endOfFile.ToString(), Encoding.Default);
        }        


        private void EvaluateAssertions()
        {
            if (dts.DtsFormula.Formulas.Count + dts.DtsFormula.ConsistencyAssertions.Count > 0)
            {
                CreateFormulaOutputInstance();
            }

            var appenders = log4net.LogManager.GetRepository().GetAppenders();            

            if (dts.DtsFormula.ExistenceAssertions.Count > 0)
            {
                ChangeLayoutAppender(appenders, "%message");

                log.Info("<h2> Existence Assertions </h2>");

                TableHead();

                ChangeLayoutAppender(appenders, layoutPattern);

                foreach (ExistenceAssertion existenceAssertion in dts.DtsFormula.ExistenceAssertions)
                {
                    Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating formula..." + (existenceAssertion.Id != null ? existenceAssertion.Id : existenceAssertion.Label)));
                    existenceAssertion.EvaluateAssertion(dts.Instance, this);
                }

                ChangeLayoutAppender(appenders, "%message");

                EndTable();
            }

            if (dts.DtsFormula.ValueAssertions.Count > 0)
            {
                ChangeLayoutAppender(appenders, "%message");

                log.Info("<h2> Value Assertions </h2>");

                TableHead();

                ChangeLayoutAppender(appenders, layoutPattern);

                foreach (ValueAssertion valueAssertion in dts.DtsFormula.ValueAssertions)
                {
                    Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating formula..." + (valueAssertion.Id != null ? valueAssertion.Id : valueAssertion.Label)));
                    valueAssertion.EvaluateAssertion(dts.Instance, this);
                }

                ChangeLayoutAppender(appenders, "%message");

                EndTable();
            }

            if (dts.DtsFormula.ConsistencyAssertions.Count > 0)
            {               
                ChangeLayoutAppender(appenders, "%message");

                log.Info("<h2> Consistency Assertions </h2>");

                TableHead();

                ChangeLayoutAppender(appenders, layoutPattern);

                foreach (ConsistencyAssertion consistencyAssertion in dts.DtsFormula.ConsistencyAssertions)
                {
                    Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating formula..." + (consistencyAssertion.Id != null ? consistencyAssertion.Id : consistencyAssertion.Label)));
                    consistencyAssertion.EvaluateAssertion(dts.Instance, this);
                }

                ChangeLayoutAppender(appenders, "%message");

                EndTable();
            }

            ChangeLayoutAppender(appenders, layoutPattern);
        }


        private static void SetAppendersLevel(Level level)
        {
            var appenders = log4net.LogManager.GetRepository().GetAppenders();  

            foreach (var appender in appenders.OfType<AppenderSkeleton>())
            {
                if (appender.Name == "RichTextBoxAppender")
                {
                    appender.Threshold = level;
                }

                if (appender.Name == "RollingFileAppender")
                {
                    appender.Threshold = level;
                }
            }
        }


        private static string GetRootLogger()
        {
            Hierarchy h = (Hierarchy)log4net.LogManager.GetRepository();
            Logger rootLogger = h.Root;
            return rootLogger.Level.Name;            
        }


        private static void TableHead()
        {
            log.Info(@"	<table width=""95%"" cellspacing=""2"" cellpadding=""5"" border=""0"" class=""details"">");
            log.Info(@"	   <tbody><tr valign=""top"">");
            log.Info(@"	      <th> id </th>");
            log.Info(@"	      <th> satisfied </th>");
            log.Info(@"	      <th> message </th>");
            log.Info(@"	   </tr>");
        }


        private static void EndTable()
        {
            log.Info(@"	   </tbody>");
            log.Info(@"     </table>");
        }


        private static void ChangeLayoutAppender(IAppender[] appenders, string layout)
        {
            foreach (var rollingFileAppender in appenders.OfType<log4net.Appender.RollingFileAppender>())
            {
                if (rollingFileAppender.Name == "FormulaRollingHtmlFileAppender")
                {
                    rollingFileAppender.Layout = new log4net.Layout.PatternLayout(layout);
                }
                
            }            
        }


        private void EvaluateAssertionSets()
        {
            foreach (AssertionSet assertionSet in dts.DtsFormula.AssertionSets)
            {
                foreach (ValueAssertion valueAssertion in assertionSet.ValueAssertions)
                {
                    valueAssertion.EvaluateAssertion(dts.Instance, this);
                }

                foreach (ExistenceAssertion existenceAssertion in assertionSet.ExistenceAssertions)
                {
                    existenceAssertion.EvaluateAssertion(dts.Instance, this);
                }

                foreach (ConsistencyAssertion consistencyAssertion in assertionSet.ConsistencyAssertions)
                {
                    consistencyAssertion.EvaluateAssertion(dts.Instance, this);
                }
            }
        }


        private void EvaluateFormulas()
        {
            int k = 0;

            foreach (FormulaXBRL formula in dts.DtsFormula.Formulas)
            {
                k = k + 1;
                Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating formula..." + (formula.Id != null ? formula.Id : formula.Label)));
                formula.EvaluateFormula(dts.Instance, this, k);
            }

            if (dts.DtsFormula.Formulas.Count + dts.DtsFormula.ConsistencyAssertions.Count > 0)
            {
                xbrl.Add(new XComment("\r\n------Computed Items-------\r\n"));

                foreach (XElement item in ItemsFormula)
                {
                    xbrl.Add(item);
                }

                doc.Add(xbrl);

                doc.Save(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\Tapioca\\FormulaOutputInstance.xml");
            }
        }


        private void CreateFormulaOutputInstance()
        {
            doc = new XDocument();

            xbrl = new XElement(xbrli + "xbrl");

            XNamespace bcb = "http://www.bcb.gov.br/taxonomies/br/bcb/top50/2012-30-09";
            xbrl.Add(new XAttribute(XNamespace.Xmlns + "bcb", "http://www.bcb.gov.br/taxonomies/br/bcb/top50/2012-30-09"));
            xbrl.Add(new XAttribute(XNamespace.Xmlns + "iso4217", "http://www.xbrl.org/2003/iso4217"));
            xbrl.Add(new XAttribute(XNamespace.Xmlns + "link", "http://www.xbrl.org/2003/linkbase"));
            xbrl.Add(new XAttribute(XNamespace.Xmlns + "xlink", "http://www.w3.org/1999/xlink"));

            if (dts.Instance != null)
            {
                XElement schemaRef = dts.Instance.ThisXDocument.Root.Element(link + "schemaRef");
                xbrl.Add(schemaRef);
            }
        }        

    }
}
